#include <iostream>
#include <fstream>
#include <string>
#include "Funcion.h"
#include "Parametro.h"
#include "FuncionesAuxiliares.h"
#include "CasoDeTest.h"

#ifndef WIN32
    #include <unistd.h>
#endif

using namespace std;

void parsearFuncion(ifstream& in, ofstream& out, string nombreFuncion);
void editarEvaluar(Funcion& funcion, ifstream& entradaEvaluar, ofstream& salidaEvaluar);
void parsearCasosDeTest(ifstream& entradaCasosDeTest,ofstream& salidaCasosDeTest, string reemplazar);
int damePosicionDeLaBarra(string reemplazar);

/*
 * El primer parametro es el archivo de definicion.
 * El segundo parametro es el archivo con los casos de test.
 * El tercer parametro es el archivo de resultados (opcional)
 */
int main(int argc, char *argv[]) {

    if ( argc < 3 ) {
        cout << "Error." << endl;
        cout << "Modo de uso: " << argv[0] << " ARCH_DEFINCION ARCH_CASOS_DE_TEST ARCH_RESULTADOS" << endl;

        return 1;
    }

    // Parsear el archivo de definicion y armar la Funcion
    ifstream archivoDefinicion(argv[1], ios::in);

    if ( !archivoDefinicion.is_open() ) {
        cout << "Error abriendo el archivo de definicion" << endl;
        return 1;
    }

    Funcion funcion;

    FuncionesAuxiliares::parseaFuncion(archivoDefinicion, funcion);

    archivoDefinicion.close();

    // Parsear el archivo de assembler reemplazando malloc y free
    string rutaAssembler = "ArchivosAssembler/" + funcion.getNombreArchivo();
    ifstream entradaAssembler(rutaAssembler.c_str(), ios::in);
    ofstream salidaAssembler("Temp/salida.asm", ios::out);

    if ( !entradaAssembler.is_open() ) {
        cout << "Error abriendo el archivo de entrada assembler." << endl;
        return 1;
    }
    if ( !salidaAssembler.is_open() ) {
        cout << "Error abriendo el archivo de salida assembler." << endl;
        return 1;
    }

    parsearFuncion(entradaAssembler,salidaAssembler,funcion.getNombre());

    entradaAssembler.close();
    salidaAssembler.close();

    // Editar el metodo Evaluar de Funcion para incluir el llamado a la funcion a testear
    ifstream entradaEvaluar("src/FuncionSinEvaluar.cpp", ios::in);
    ofstream salidaEvaluar("src/Funcion.cpp", ios::out);

    if ( !entradaEvaluar.is_open() ) {
        cout << "Error abriendo el archivo FuncionSinEvaluar.cpp" << endl;
        return 1;
    }
    if ( !salidaEvaluar.is_open() ) {
        cout << "Error abriendo el archivo Funcion.cpp" << endl;
        return 1;
    }

    editarEvaluar(funcion, entradaEvaluar, salidaEvaluar);

    entradaEvaluar.close();
    salidaEvaluar.close();

    // Parsear el archivo cpp con los casos de test
    ifstream entradaCasosDeTest(argv[2], ios::in);
    ofstream salidaCasosDeTest("Temp/MiTest.cpp", ios::out);

    if ( !entradaCasosDeTest.is_open() ) {
        cout << "Error abriendo el archivo de entrada de casos de test." << endl;
        return 1;
    }
    if ( !salidaCasosDeTest.is_open() ) {
        cout << "Error abriendo el archivo de salida de casos de test." << endl;
        return 1;
    }

    string reemplazar(argv[2]);
    int punto = reemplazar.find_last_of(".");
    int posicionDeLaBarra = damePosicionDeLaBarra(reemplazar);
    if (posicionDeLaBarra != 0) {
        reemplazar = reemplazar.substr(posicionDeLaBarra + 1, punto - posicionDeLaBarra - 1);
    } else {
        reemplazar = reemplazar.substr(0, punto);
    }

    // debug
    cout << endl << "DEBUG -> NOMBRE DEL CASO DE TEST A PARSEAR: " << reemplazar << endl << endl;
    // end debug

    parsearCasosDeTest(entradaCasosDeTest,salidaCasosDeTest, reemplazar);

    entradaCasosDeTest.close();
    salidaCasosDeTest.close();


    // Llamar a los scrips para que compiles el assembler y el proyecto de testing
    #ifdef WIN32
        string strArchivoDefincion(argv[1]);
        if (argc > 3) {
            string strArchivoResultados(argv[3]);
            system(("compilar.bat " + strArchivoDefincion + " " + strArchivoResultados).c_str());
        } else {
            system(("compilar.bat " + strArchivoDefincion).c_str());
        }
    #else
        execve("./compilar.sh", argv, NULL);
    #endif

    return 0;
}

int damePosicionDeLaBarra(string reemplazar){
    unsigned int posicion = reemplazar.find_last_of("\\");

    if(posicion == string::npos){
        posicion = reemplazar.find_last_of("/");

        if(posicion == string::npos){
            return 0;
        }

        return posicion;

    } else {
        return posicion;
    }
}

/*
 * Toma el archivo con la implementacion de la funcion y reemplaza los llamados
 * a free y a malloc por los nuestros.
 */
void parsearFuncion(ifstream& in, ofstream& out,string nombreFuncion) {
    string linea;

    string malloc = "malloc";
    string free = "free";

    #ifdef WIN32
        string malloc2 = "_malloc2";
        string free2 = "_free2";
        string funcion = "_" + nombreFuncion;
    #else
        string malloc2 = "malloc2";
        string free2 = "free2";
    #endif

    while ( !in.eof() ) {
        getline(in,linea);

        size_t encontreMalloc = linea.find(malloc);
        size_t encontreFree = linea.find(free);

        if ( encontreMalloc != string::npos ) {
            linea.replace(encontreMalloc,malloc.length(),malloc2);
        }

        if ( encontreFree != string::npos ) {
            linea.replace(encontreFree,free.length(),free2);
        }

        #ifdef WIN32
        size_t encontreFuncion = linea.find(nombreFuncion);

        if ( encontreFuncion != string::npos ) {
            linea.replace(encontreFuncion,nombreFuncion.length(),funcion);
        }
        #endif

        out << linea << endl;
    }

}

void editarEvaluar(Funcion& func, ifstream& entradaEvaluar, ofstream& salidaEvaluar) {

    string linea;

    while ( !entradaEvaluar.eof() ) {
        getline(entradaEvaluar,linea);
        size_t encontreLinea = linea.find("/* aca va la funcion a evaluar */");

        if ( encontreLinea != string::npos ) {

            if(func.getParametroSalida().getClaseDeDato() != C_VOID){

                string claseRetorno = FuncionesAuxiliares::parsearStringClase(func.getParametroSalida().getClaseDeDato());

                salidaEvaluar << "\t" << claseRetorno << "* resultado;" << endl;

                salidaEvaluar << "\tresultado = new " << claseRetorno << "( " ;
            };

            vector<Parametro>& parametros = func.getParametros();

             salidaEvaluar << func.getNombre() << "(";

            for (unsigned int i = 0; i < parametros.size(); i++ ) {
                if(parametros[i].getEsValor()){
                    string clase = FuncionesAuxiliares::parsearStringClase(parametros[i].getClaseDeDato());

                    salidaEvaluar <<  " ((" << clase << "*) parametros[" << i <<  "])->dameValor()";
                } else {
                    salidaEvaluar <<  " parametros[" << i << "]->damePunteroADato()";
                }

                if(i != parametros.size() - 1 ){
                        salidaEvaluar << "," ;
                }
            }

            if(func.getParametroSalida().getClaseDeDato() != C_VOID){
                salidaEvaluar << ")" ;
            };

            salidaEvaluar << " );" << endl;

            if(func.getParametroSalida().getClaseDeDato() != C_VOID){
                salidaEvaluar << endl << "\tcasoDeTest.setSalidaObtenida(resultado);" << endl;
            }

        } else {
            salidaEvaluar << linea << endl;
        }
    }

}

void parsearCasosDeTest(ifstream& entradaCasosDeTest,ofstream& salidaCasosDeTest, string reemplazar) {

    salidaCasosDeTest << "#include \"../src/MiTest.h\"" << endl << endl;

    string linea;

    while ( !entradaCasosDeTest.eof() ) {
        getline(entradaCasosDeTest, linea);

        size_t encontre = linea.find(reemplazar);

        if ( encontre != string::npos ) {
            linea.replace(encontre,reemplazar.length(),"MiTest");
        }

        salidaCasosDeTest << linea << endl;
    }
}

